#!/usr/bin/php
<?php
/* Copyright 2015, Guilherme Jardim
 * Copyright 2016-2022, Dan Landon
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version 2,
 * as published by the Free Software Foundation.
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 */

$plugin = "unassigned.devices";
$docroot = $docroot ?: @$_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp';
require_once("$docroot/plugins/{$plugin}/include/lib.php");

$COMMAND = $argv[1];
$DEVNAME = (isset($_ENV['DEVNAME'])) ? $_ENV['DEVNAME'] : ( isset($argv[2]) ? $argv[2] : NULL );
$DEVNAME = (file_exists($DEVNAME)) ? realpath($DEVNAME) : $DEVNAME;
$dev_pos = strpos($DEVNAME, 'name=');
if ($dev_pos !== false) {
	$dev_name		= substr($DEVNAME, $dev_pos+5);
	foreach (get_all_disks_info() as $disk) {
		if ($disk['unassigned_dev'] == $dev_name) {
			if ($COMMAND != 'spindown') {
				$DEVNAME = basename($disk['device']);
			} else {
				$DEVNAME = basename($disk['ud_dev']);
			}
			break;
		}
	}
} elseif ($COMMAND != 'spindown') {
	$sf = $paths['dev_state'];
	if (is_file($sf) && (strpos(basename($DEVNAME), 'dev') !== false)) {
		$devs = @parse_ini_file($sf, true);
		$DEVNAME = isset($devs[$DEVNAME]) ? $devs[$DEVNAME]['device'] : $DEVNAME;
	}
} else {
	$sf = $paths['dev_state'];
	if (is_file($sf) && (strpos(basename($DEVNAME), 'dev') === false)) {
		$DEVNAME = get_disk_dev(MiscUD::base_device(basename($DEVNAME)));
	}
}

/* If the DEVNAME is empty, the device is not defined - exit with an error. */
if (! $DEVNAME) {
	unassigned_log("rc.unassigned died with no DEVNAME");
	die("Fail: device not defined.\n");
}

/* Create array for mount button status files. */
$remove = array();

/* Mount devices. */
function unassigned_mount() {
	global $DEVNAME, $paths;

	/* Mount local disks. */
	foreach (get_unassigned_disks() as $name => $disk) {
		$device = $disk['device'];
		foreach ($disk['partitions'] as $partition)
		{
			if ( preg_match("#".$DEVNAME."#i", realpath($partition)) || $DEVNAME == "auto" || $DEVNAME == "autodevices" ) {
				$info = get_partition_info($partition);

				unassigned_log("Partition found with the following attributes: ".(implode(', ', array_map(function($v, $k){$v = (strpos($k, "pass") !== false) && (strpos($k, "pass_through") === false)? "*******" : $v; return "$k='$v'"; }, $info, array_keys($info)))), $GLOBALS['UDEV_DEBUG']);

				/* If disk is passed through, skip to the next disk. */
				if ($info['pass_through']) {
					unassigned_log("Disk with serial '{$info['serial']}', mountpoint '".basename($info['mountpoint'])."' is set as passed through.");
					break;
				}

				/* If disk is formatting, skip to the next one. */
				if (array_values(preg_grep("@/formatting_".basename($device)."@i", listDir(dirname($paths['formatting']))))[0]) {
					break;
				}

				/* If disk is not set to auto mount, skip to the next disk. */
				if (! $info['automount'] && ( $DEVNAME == "auto" || $DEVNAME == "autodevices" || isset($_ENV['DEVNAME']) )) {
					if ($info['fstype']) {
						unassigned_log("Disk with ID '{$info['serial']} (".MIscUD::base_device(basename($device)).")' is not set to auto mount.");
					}

					break;
				}

				/* If the device doesn't have a serial number it cannot be mounted. */
				if (! $info['serial']) {
					unassigned_log("Disk '".basename($device)."' does not have a serial number and cannot be mounted.");
					break;
				}

				/* If partition does not have a file system it cannot be mounted. */
				if (! $info['fstype']) {
					unassigned_log("Partition '".basename($info['device'])."' does not have a file system and cannot be mounted.");
					continue;
				}

				/* See if the mountpoint is a duplicate reserved, share, or UD name. Don't mount if a duplicate.*/
				if (! check_for_duplicate_share(($info['fstype'] == "crypto_LUKS" ? $info['luks'] :  $info['device']), basename($info['mountpoint']))) {
					unassigned_log("Disk with serial '{$info['serial']}', mountpoint '".basename($info['mountpoint'])."' cannot be mounted.");

					/* Execute device script with error mounting action. */
					execute_script($info, "ERROR_MOUNT");
					continue;
				}

				if ($info['device']) {
					unassigned_log("Disk found with the following attributes: ".(implode(', ', array_map(function($v, $k){$v = ((strpos($k, "pass") !== false) && (strpos($k, "pass_through") === false)) ? "*******" : $v; return "$k='$v'"; }, $info, array_keys($info)))), $GLOBALS['UDEV_DEBUG']);
					unassigned_log("Adding partition '".basename($info['device'])."'...");

					/* Create the mounting status file for this device. */
					if ($info['fstype'] != "crypto_LUKS") {
						addFile(sprintf($paths['mounting'], basename($info['device'])));
					} else {
						addFile(sprintf($paths['mounting'], basename($info['luks'])));
					}

					unassigned_log("Mounting partition '".basename($info['device'])."' at mountpoint '{$info['mountpoint']}'...");

					/* Cannot mount disk with 'UNRAID' label. */
					if ($info['label'] == "UNRAID") {
						unassigned_log("Error: Cannot mount device '{$info['device']}' with label 'UNRAID'.");

						/* Execute device script with error mount action. */
						execute_script($info, "ERROR_MOUNT");
					} else {
						/* Mount the disk. */
						if (do_mount( $info )) 
						{
							/* Add smb and nfs shares for this device. */
							if ($info['shared']) {
								$fat_fruit	= (($info['fstype'] == "vfat") || ($info['fstype'] == "exfat")) ? true : false;
								add_smb_share($info['mountpoint'], true, $fat_fruit);
								add_nfs_share($info['mountpoint']);
							}

							/* Execute device script with add action. */
							execute_script($info, "ADD");

							/* Update the partition info. */
							$info = get_partition_info($partition);
							export_disk($info, true);
						} else if (! is_mounted($info['device'])) {
							/* There was an error mounting the disk. */
							unassigned_log("Partition '".basename($info['label'])."' cannot be mounted.");

							/* Execute device script with error unmount action. */
							execute_script($info, "ERROR_MOUNT");
						}
					}
				} else {
					unassigned_log("Error: Cannot mount null device with serial '{$info['serial']}'.");
				}
			}
		}
	}

	/* Mount Remote mounts. */
	if (strpos($DEVNAME, "//") === 0 || strpos($DEVNAME, ":/") || $DEVNAME == "auto" || $DEVNAME == "autoshares") {
		foreach (get_samba_mounts() as $info) {
			$device = $info['device'];
			if ( $DEVNAME == $device || $DEVNAME == "auto" || $DEVNAME == "autoshares" ) {

				/* if remote mount is not set to auto mount, go to the next one. */
				if (! $info['automount'] && ($DEVNAME == "auto" || $DEVNAME == "autoshares")) {
					unassigned_log("Remote Share '".$info['device']."' is not set to auto mount.");
					continue;
				}

				/* See if the mountpoint is a duplicate reserved, share, or UD name. Don't mount if a duplicate.*/
				if (! check_for_duplicate_share($info['device'], basename($info['mountpoint']))) {
					unassigned_log("Remote Share '".$info['device']."' cannot be mounted.");

					/* Execute device script with error mounting action. */
					execute_script($info, "ERROR_MOUNT");
					continue;
				}

				unassigned_log("Remote Share found with the following attributes: ".(implode(', ', array_map(function($v, $k){$v = ((strpos($k, "pass") !== false) || ((strpos($k, "user") !== false) && (strpos($k, "user_command") === false))) ? "*******" : $v; return "$k='$v'"; }, $info, array_keys($info)))), $GLOBALS['UDEV_DEBUG']);

				/* Remove any special characters. */
				$mount_device = basename($info['device'])."_".$info['fstype'];

				/* Create mounting status file. */
				addFile(sprintf($paths['mounting'], $mount_device));

				unassigned_log("Mounting Remote Share '{$info['device']}'...");

				/* Mount the remote share. */
				if (do_mount( $info )) {
					if ($info['smb_share']) {
						/* Add smb share for the remote share. */
						add_smb_share($info['mountpoint'], $info['fstype'] == "root" ? true : false);

						/* Update the samba mount status. */
						foreach (get_samba_mounts() as $info) {
							$device = $info['device'];
							if ( $DEVNAME == $device ) {
								export_disk($info, true);
								break;
							}
						}
					}

					/* Execute remote mount script with add action. */
					execute_script($info, "ADD");
				} else {
					/* Execute remote mount script with error mount action. */
					execute_script($info, "ERROR_MOUNT");
				}
			}
		}
	}

	/* Mount ISO File mounts. */
	if (strpos($DEVNAME, "/mnt") === 0 || $DEVNAME == "auto" || $DEVNAME == "autodevices") {
		foreach (get_iso_mounts() as $info) {
			$device = $info['device'];
			if ( $DEVNAME == $device || $DEVNAME == "auto" || $DEVNAME == "autodevices" ) {

				/* If iso mount is not set to auto mount, skip to the next one. */
				if (! $info['automount'] && ($DEVNAME == "auto" || $DEVNAME == "autodevices")) {
					unassigned_log("ISO File '".$info['device']."' is not set to auto mount.");
					continue;
				}

				/* See if the mountpoint is a duplicate reserved, share, or UD name. Don't mount if a duplicate.*/
				if (! check_for_duplicate_share($info['device'], basename($info['mountpoint']))) {
					unassigned_log("ISO File '".$info['device']."' cannot be mounted.");

					/* Execute device script with error mounting action. */
					execute_script($info, "ERROR_MOUNT");
					continue;
				}

				unassigned_log("ISO File share found with the following attributes: ".(implode(', ', array_map(function($v, $k){$v = (strpos($k, "pass") !== false) ? "*******" : $v; return "$k='$v'"; }, $info, array_keys($info)))), $GLOBALS['UDEV_DEBUG']);

				/* Remove any special characters. */
				$mount_device = basename($info['device']);

				/* Create mounting status file. */
				addFile(sprintf($paths['mounting'], $mount_device));

				unassigned_log("Mounting ISO File '{$info['device']}'...");

				/* Mount the iso file. */
				if (do_mount( $info )) {
					/* Add smb and nfs shares for the iso file. */
					add_smb_share($info['mountpoint']);
					add_nfs_share($info['mountpoint']);

					/* Execute iso file script with add action. */
					execute_script($info, "ADD");

					/* Update the iso mount status. */
					foreach (get_iso_mounts() as $info) {
						$device = $info['device'];
						if ( $DEVNAME == $device ) {
							export_disk($info, true);
							break;
						}
					}
				} else {

					/* Execute ios file script with error mount action. */
					execute_script($info, "ERROR_MOUNT");
				}
			}
		}
	}

	/* A udev mount event? */
	if (isset($_ENV['DEVTYPE']) && ($_ENV['DEVTYPE'] == "partition")) {
		unassigned_log("Mount: Received a udev 'add partition'.", $GLOBALS['UDEV_DEBUG']);

		/* Set flag to tell Unraid to update devs.ini file of unassigned devices. */
		@file_put_contents($paths['hotplug_event'], "");
	}
}

/* Unmount devices. */
function unassigned_umount() {
	global $DEVNAME, $paths;

	$force = ($DEVNAME == "all") ? true : false;

	/* Unmount local disks. */
	foreach(get_unassigned_disks() as $disk) {
		$device = $disk['device'];
		foreach ($disk['partitions'] as $partition) {
			if ( preg_match("#".$DEVNAME."#i", realpath($partition)) || $DEVNAME == "auto" || $DEVNAME == "all" ) {
				$info = get_partition_info($partition);
				if (! $info['automount'] && $DEVNAME == "auto" ) {
					continue;
				}

				unassigned_log("Partition found with the following attributes: ".(implode(', ', array_map(function($v, $k){$v = (strpos($k, "pass") !== false) && (strpos($k, "pass_through") === false)? "*******" : $v; return "$k='$v'"; }, $info, array_keys($info)))), $GLOBALS['UDEV_DEBUG']);

				/* Cannot unmount disk with 'UNRAID' label. */
				if ($info['label'] == "UNRAID") {
					unassigned_log("Error: Cannot unmount device '{$info['device']}' with label 'UNRAID'.");

					/* Execute device script with error unmount action. */
					execute_script($info, "ERROR_UNMOUNT");
				} else {
					/* If the device is mounted, unmount it. */
					if ( is_mounted($info['device']) || is_mounted($info['mountpoint']) )
					{
						/* Create unmounting status file for this device. */
						if ($info['fstype'] != "crypto_LUKS") {
							addFile(sprintf($paths['unmounting'], basename($info['device'])));
						} else {
							addFile(sprintf($paths['unmounting'], basename($info['luks'])));
						}

						/* Remove smb and nfs shares for this device. */
						if ( rm_smb_share($info['target']) && rm_nfs_share($info['target']) ) {
							/* Execute device script with unmount action. */
							execute_script($info, "UNMOUNT");

							unassigned_log("Unmounting partition '".basename($info['device'])."' at mountpoint '{$info['mountpoint']}'...");

							/* Unmount the device. */
							if (do_unmount($info['device'], $info['mountpoint'])) {
								if ($info['fstype'] == "crypto_LUKS" ) {
									shell_exec("/sbin/cryptsetup luksClose ".basename($info['device']));
								}

								/* Execute device script with remove actioon. */
								execute_script($info, "REMOVE");
								export_disk($info, false);
							} else {
								unassigned_log("Partition '".basename($info['label'])."' cannot be unmounted.");

								/* Execute device script with error unmount action. */
								execute_script($info, "ERROR_UNMOUNT");
							}
						}
					}
				}
			}
		}
	}

	/* Unmount Remote SMB/NFS mounts. */
	if (strpos($DEVNAME, "//") === 0 || strpos($DEVNAME, ":/") || $DEVNAME == "auto" || $DEVNAME == "all") {
		foreach (get_samba_mounts() as $info) {
			$device = $info['device'];
			if ( $DEVNAME == $device || $DEVNAME == "auto" || $DEVNAME == "all" ) {
				if (! $info['automount'] && $DEVNAME == "auto" ) {
					continue;
				}
				unassigned_log("Remote SMB/NFS share found with the following attributes: ".(implode(', ', array_map(function($v, $k){$v = (strpos($k, "pass") !== false) ? "*******" : $v; return "$k='$v'"; }, $info, array_keys($info)))), $GLOBALS['UDEV_DEBUG']);

				$force_unmount = $info['fstype'] == "root" ? false : ($info['is_alive'] ? $force : true);

				/* Unmount the remote share if it is mounted. */
				$dev	= $info['fstype'] == "root" ? $info['mountpoint'] : $info['device'];
				if ( is_mounted(($info['fstype'] == "cifs") ? "//".$info['ip']."/".$info['path'] : $dev) ) {
					/* Remove special characters. */
					$mount_device = basename($dev)."_".$info['fstype'];

					/* Create unmounting status file. */
					addFile(sprintf($paths['unmounting'], $mount_device));

					unassigned_log("Removing Remote SMB/NFS share '{$info['device']}'...");

					/* Execute the remote mount script file with unmount action. */
					execute_script($info, "UNMOUNT");

					unassigned_log("Unmounting Remote SMB/NFS Share '{$info['device']}'...");

					$smb = $info['fstype'] == "cifs" ? true : false;
					$nfs = $info['fstype'] == "nfs" ? true : false;

					/* Unmount the remote share. */
					if ( do_unmount(($info['fstype'] == "cifs") ? "//".$info['ip']."/".$info['path'] : $dev, $info['mountpoint'], $force_unmount, $smb, $nfs) ) {
						if ( rm_smb_share($info['mountpoint']) ) {
							/* Execute remote mount script with remove action. */
							execute_script($info, "REMOVE");
							export_disk($info, false);
						}
					} else {
						/* Execute remote mount script with error unmount action. */
						execute_script($info, "ERROR_UNMOUNT");
					}
				} else {
					unassigned_log("Remote SMB/NFS share '{$info['device']}' is not mounted.");
				}
			}
		}
	}

	/* Unmount ISO File mounts. */
	if (strpos($DEVNAME, "/mnt") === 0 || $DEVNAME == "auto" || $DEVNAME == "all") {
		foreach (get_iso_mounts() as $info) {
			$device = $info['device'];
			if ( $DEVNAME == $device || $DEVNAME == "auto" || $DEVNAME == "all" ) {
				if (! $info['automount'] && $DEVNAME == "auto" ) {
					continue;
				}
				unassigned_log("ISO File share found with the following attributes: ".(implode(', ', array_map(function($v, $k){$v = (strpos($k, "pass") !== false) ? "*******" : $v; return "$k='$v'"; }, $info, array_keys($info)))), $GLOBALS['UDEV_DEBUG']);

				/* If iso file is mounted, unmlount it. */
				if ( is_mounted($info['mountpoint']) ) {
					/* Create mounting status file. */
					addFile(sprintf($paths['unmounting'], basename($info['device'])));

					unassigned_log("Removing ISO File share '{$info['device']}'...");

					/* Execute iso script file with actyion unmount. */
					execute_script($info, "UNMOUNT");

					unassigned_log("Unmounting ISO File '{$info['device']}'...");

					/* Unmount the iso file. */
					if ( do_unmount($info['mountpoint'], $info['mountpoint'], $force) ) {
						/* Remove the smb and nfs shares. */
						if ( rm_smb_share($info['mountpoint']) && rm_nfs_share($info['mountpoint']) ) {
							/* Execute the iso file script with the remove action. */
							execute_script($info, "REMOVE");
							export_disk($info, false);
						}
					} else {
						/* Execute the iso script with the error unmount action. */
						execute_script($info, "ERROR_UNMOUNT");
					}
				} else {
					unassigned_log("Remote ISO File share '{$info['device']}' is not mounted.");
				}
			}
		}
	}
}

/* Update udev disk info. */
function unassigned_reload() {
	global $paths;

	if ($_ENV['DEVTYPE'] == "disk" || $_ENV['DEVTYPE'] == "partition") {

		if ($_ENV['DEVTYPE'] == "disk") {
			$str = "remove disk";
		} else {
			$str = "change partition";
		}

		unassigned_log("Reload: A udev '".$str."' initiated a reload of udev info.", $GLOBALS['UDEV_DEBUG']);

		if (isset($_ENV['DEVLINKS'])) {
			unassigned_log("Updating udev information...", $GLOBALS['UDEV_DEBUG']);

			foreach (explode(" ", $_ENV['DEVLINKS']) as $link) {
				get_udev_info($link, $_ENV);
			}
		}

		/* Set flag to tell Unraid to update devs.ini file of unassigned devices. */
		sleep(1);
		@file_put_contents($paths['hotplug_event'], "");
	}
}

/* A hotplug disk event has arrived. */
function unassigned_hotplug() {
	global $DEVNAME, $paths;

	if ($_ENV['DEVTYPE'] == "disk") {

		unassigned_log("Hotplug: A udev 'add disk' initiated a Hotplug event.", $GLOBALS['UDEV_DEBUG']);

		if (isset($_ENV['DEVLINKS'])) {
			unassigned_log("Updating udev information...", $GLOBALS['UDEV_DEBUG']);

			foreach (explode(" ", $_ENV['DEVLINKS']) as $link) {
				get_udev_info($link, $_ENV);
			}
		}

		/* Set flag to tell Unraid to update devs.ini file of unassigned devices. */
		sleep(1);
		@file_put_contents($paths['hotplug_event'], "");
	}
}

/* Spin down a disk using Unraid api. */
function unassigned_spin_down() {
	global $DEVNAME;

	if (! is_file("/tmp/unassigned.devices/shut_down")) {
		$dev = basename($DEVNAME);
		MiscUD::spin_disk(true, $dev);
	}
}

/* Detach the disk device. */
function unassigned_detach($ata) {
	global $DEVNAME;

	$dev = MiscUD::base_device(basename($DEVNAME));

	if (! is_file("/tmp/unassigned.devices/shut_down")) {
		/* We only allow removing USB devices. */
		foreach (get_all_disks_info() as $disk) {
			if (basename($disk['device']) == $dev) {
				if (($disk['id_bus'] == "usb") || ($ata)) {
					foreach ($disk['partitions'] as $partition) {
						if ($partition['mounted']) {
							$mounted = true;
						}
					}
					if (! $mounted) {
						if (is_dir("/sys/block/{$dev}/")) {
							exec("echo 'offline' > /sys/block/{$dev}/device/state");
							exec("echo '1' > /sys/block/{$dev}/device/delete");
							unassigned_log("Device '{$dev}' has been detached.");
						}
					} else {
						unassigned_log("Device '{$DEVNAME}' is mounted and cannot be detached!");
					}
				} else {
					unassigned_log("Device '{$dev}' is not a USB device and cannot be detached!");
				}
				break;
			}
		}
	}
}

/* Attach a disk device that is detached. */
function unassigned_attach() {
	global $DEVNAME;

	/* Get this device's hostX. */
	$host	= MiscUD::get_device_host($DEVNAME);

	if ($host) {
		@file_put_contents("/sys/class/scsi_host/${host}/scan", "- - -");
	}
}

/* Add status file so mount buttons will show current status of operation. */
function addFile($file) {
	global $remove;

	$file = safe_name($file);

	@touch($file);
	$remove[] = $file;
}

/* Update json mounted disk status. */
function export_disk($disk, $add) {
	global $paths;

	$info	= MiscUD::get_json($paths['mounted']);
	$dev	= $disk['device'];
	if ($add)
	{
		if (isset($disk["pass"])) {
			unset($disk["pass"]);
		}
		$info[$dev] = $disk;
	}
	else
	{
		unset($info[$dev]);
	}

	MiscUD::save_json($paths['mounted'], $info);	
}

switch ($COMMAND) {
	case 'mount':
		unassigned_mount();
		break;

	case 'umount':
		unassigned_umount();
		break;

	case 'reload':
		unassigned_reload();
		break;

	case 'hotplug':
		unassigned_hotplug();
		break;

	case 'spindown':
		unassigned_spin_down();
		break;

	case 'detach':
		$ata = ($argv[3] == "true") ? true : false;
		unassigned_detach($ata);
		break;

	case 'attach':
		unassigned_attach();
		break;

	case 'refresh':
		break;

	default:
		unassigned_log("Error: 'rc.unassigned {$argv[1]} {$argv[2]}' not understood");
		unassigned_log("rc.unassigned usage: 'mount', 'umount', 'reload', 'hotplug', 'spindown', 'detach', 'attach', 'refresh'");
		exit(0);
		break;
}

/* Clear all the status files. */
array_map(function($f){@unlink($f);}, $remove);
?>
